class pose:
""" used to store the pose of the robot """
def __init__(self, x, y, yaw, id, endTranslation):
# end translation is distance from point to wall in direction of travel from previous point
self.x = x
self.y = y
self.yaw = yaw # degrees
self.endTranslation = endTranslation/12 # in to feet
self.id = id
def __str__(self):
""" String representation of the pose """
return "Pose: x: " + str(self.x) + " y: " + str(self.y) + " yaw: " + str(self.yaw) + " id: " + str(self.id)
# starting point
pose_1 = pose(-4, -3, 90, 0, 0) # 1
pose_2 = pose(-4, -1, 0, 1, 0)
# drive though second point and third point
pose_3 = pose(2, -1, 0, 2, 0) # 2, 3
# drive to fourth point
pose_4 = pose(2, -3, -90, 3, 18) # 4
# drive to the fifth point
pose_5 = pose(5, -3, 0, 4, 18) # 5
# pose_6 = pose(5, -2, 90, 5)
# drive to through the sixth point to the seventh point
pose_7 = pose(5, 3, 90, 6, 18) # 6, 7
# drive to the eighth point
pose_8 = pose(0, 3, -180, 7, 31.5) # 8
# drive to the ninth point, the end
pose_9 = pose(0, 0, -90, 8, 0) # 9
class action:
""" used to store information about an action to send to the robot """
def __init__(self, is_translation, value, id):
self.is_translation = is_translation # if is a translation or a turn
self.value = value
self.id = id
# does not include the first pose since is starting point
waypoints = [pose_2, pose_3, pose_4, pose_5, pose_7, pose_8, pose_9]
class planner:
""" Planner for the robot to drive around the map and reach the goal. """
def __init__(self, waypoints = waypoints[:], printCmds = False):
""" Initialize the planner.
Args:
waypoints ([Pose]): List of waypoints to traverse
printCmds (bool): If true, print the commands to the
console instead of sending them
"""
self.waypoints = waypoints
# initialize the current pose to the first waypoint
self.currentPose = pose_1
self.targetPose = None
self.controls = []
self.printCmds = printCmds
# set the control constants
self.translationKP = 0.04
self.leftKP = 0.1
self.rightKP = 0.04
self.sleepTime = 5
def normalize_angle(self, angle):
""" Normalize the angle to be between -180 and 180 degrees """
new_angle = angle
while (new_angle < -180):
new_angle = new_angle + 360
while (new_angle >= 180):
new_angle = new_angle - 360
return new_angle
def computeControl(self):
""" Get the control information based on the
odometry motion model
Returns:
[delta_rot_1]: Rotation 1 (degrees)
[delta_trans]: Translation (meters)
[delta_rot_2]: Rotation 2 (degrees)
"""
# first rotation comes from moving from current heading to direction of next pose
vectorDirection = np.array([self.targetPose.x - self.currentPose.x, self.targetPose.y - self.currentPose.y])
# get angle between vector and x-axis
angle = np.arctan2(vectorDirection[1], vectorDirection[0])
# convert to degrees
angle = np.rad2deg(angle)
# get difference between current heading and angle
delta_rot_1 = self.normalize_angle(angle - self.currentPose.yaw)
# get distance between current and next pose
delta_translation = np.linalg.norm(vectorDirection)
# get final angle change between current and angle
delta_rot_2 = self.normalize_angle(self.targetPose.yaw - angle)
# check if are using the travel distance or the wall distance
if self.targetPose.endTranslation != 0:
delta_translation = self.targetPose.endTranslation
return delta_rot_1, delta_translation, delta_rot_2
def getNewControls(self):
""" get the new controls for the next node """
# get the control information
delta_rot_1, delta_translation, delta_rot_2 = self.computeControl()
# add to the controls
self.controls.append(action(False, delta_rot_1, 0))
self.controls.append(action(True, delta_translation, 1))
self.controls.append(action(False, delta_rot_2, 2))
def sendControlToRobot(self, control : action):
""" send a control action to the robot"""
cmdStr = ""
cmd = None
kp = 0
sleepTime = self.sleepTime
if control.is_translation:
# convert from feet to mm
control.value *= 304.8
if self.targetPose.id == 2:
sleepTime = 10
# round to nearest mm for translation, deg for rotation
value = int(round(control.value, 0))
if control.is_translation:
# set correct movement command
kp = self.translationKP
if self.printCmds:
cmdStr = "CMD.POSITION_CONTROL" if self.targetPose.endTranslation != 0 else "CMD.POSITION_CONTROL_KF"
else:
cmd = CMD.POSITION_CONTROL if self.targetPose.endTranslation != 0 else CMD.POSITION_CONTROL_KF
else:
# set correct rotation command
# left and right turns have different kp values
kp = self.leftKP if value > 0 else self.rightKP
if value == 0:
# avoid sending a rotation of 0 since that does not move the robot
return
if self.printCmds:
cmdStr = "CMD.TURN_LEFT" if value > 0 else "CMD.TURN_RIGHT"
else:
cmd = CMD.TURN_LEFT if value > 0 else CMD.TURN_RIGHT
# send the command after creating the command string
cmdString = str(kp) + "|" + str(abs(value))
if self.printCmds:
print("ble.send_command(" + cmdStr + ", " + '"' + cmdString + '"' + ")" )
print("time.sleep(" + str(sleepTime) + ")")
else:
print("Sending command: " + cmdStr + " " + cmdString + " to robot")
ble.send_command(cmd, cmdString)
time.sleep(sleepTime)
def run(self):
""" navigate through all waypoints """
i = 0
# go through each waypoint
while self.waypoints != []:
# get the next target pose
self.targetPose = self.waypoints.pop(0)
print(waypoints[i])
# get the new controls
self.getNewControls()
# send the controls to the robot
for control in self.controls:
self.sendControlToRobot(control)
print()
# update the current pose
self.currentPose = self.targetPose
# clear the controls
self.controls = []
i += 1
print("Done!")